package cn.changeforyou.web.cloud.auth.datastruct;

import cn.changeforyou.web.cloud.auth.entity.OrgInfo;
import cn.changeforyou.web.cloud.webBase.auth.OrgModel;
import org.apache.commons.lang3.ObjectUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 组织树 暂时不考虑线程安全
 * 用于判断组织的层次关系
 */
public class OrgTree {

    private Node root;
    private final Map<Integer, Node> id2NodeMap = new HashMap<>(32);

    public OrgTree() {
    }

    static class Node {
        private final Integer id;
        private Node parent;
        private List<Node> children;
        private final boolean origin;
        private OrgInfo info;

        public Node(Integer id, boolean origin, OrgInfo info) {
            this.id = id;
            this.origin = origin;
            this.info = info;
        }
    }

    /**
     * 寻找上一层组织信息
     *
     * @param id 指定元素id
     * @return 组织信息
     */
    public OrgInfo findParentInfo(Integer id) {
        Node node = getAndAssertNotContainsId(id);
        return findParentInfo(node);
    }



    /**
     * 寻找是否是从
     *
     * @param id 指定元素的id
     * @return 组织信息
     */
    public OrgInfo findOriginInfo(Integer id) {
        Node node = getAndAssertNotContainsId(id);
        return findOriginNode(node).info;
    }

    /**
     * 判断第一个元素是否在第二个元素下面包括相等
     *
     * @param first  第一个元素id
     * @param second 第二个元素id
     * @return 是否
     */
    public boolean isFirstDownAtSecond(Integer first, Integer second) {
        return isFirstUpAtSecond(second, first);
    }

    /**
     * 判断第一个元素是否在第二个元素下面
     *
     * @param first  第一个元素id
     * @param second 第二个元素id
     * @return 是否
     */
    public boolean isFirstDownSecond(Integer first, Integer second) {
        return isFirstUpSecond(second, first);
    }

    /**
     * 判断第一个元素是否在第二个元素上面包含相等
     *
     * @param first  第一个元素id
     * @param second 第二个元素id
     * @return 是否
     */
    public boolean isFirstUpAtSecond(Integer first, Integer second) {
        assertHasAnyNull(first, second);
        Node firstNode = getAndAssertNotContainsId(first);
        Node secondNode = getAndAssertNotContainsId(second);
        Node thatNode = getTheNodeIsUpAtThatNode(secondNode, firstNode);
        return null != thatNode;
    }

    /**
     * 判断第一个元素是否在第二个元素上面
     *
     * @param first  第一个元素id
     * @param second 第二个元素id
     * @return 是否
     */
    public boolean isFirstUpSecond(Integer first, Integer second) {
        assertHasAnyNull(first, second);
        Node firstNode = getAndAssertNotContainsId(first);
        Node secondNode = getAndAssertNotContainsId(second);
        Node parent = secondNode.parent;
        Node thatNode = getTheNodeIsUpAtThatNode(parent, firstNode);
        return null != thatNode;
    }

    /***
     * 比较第一个和第二个元素是否相等
     * @param first 第一个元素id
     * @param second 第二个元素id
     * @return 是否
     */
    public boolean isFirstAtSecond(Integer first, Integer second) {
        compareCheck(first, second);
        return first.equals(second);
    }

    /**
     * 获取组织模型
     * @param id 组织id
     * @return
     */
    public OrgModel getOrgModelById(Integer id) {
        Node node = getAndAssertNotContainsId(id);
        OrgModel model = new OrgModel();
        OrgInfo orgInfo = node.info;
        OrgInfo parentInfo = findParentInfo(node);
        OrgInfo originOrgInfo = findOriginOrgInfo(node);

        model.setId(id);
        model.setOrgCode(orgInfo.getOrgCode());
        model.setOrgType(orgInfo.getOrgType());
        model.setOrgName(orgInfo.getOrgName());
        model.setParentOrgCode(parentInfo.getOrgCode());
        model.setParentOrgName(parentInfo.getOrgName());
        model.setParentOrgId(parentInfo.getParentOrgId());
        model.setOriginOrgId(originOrgInfo.getId());
        model.setOriginOrgCode(originOrgInfo.getOrgCode());
        model.setOriginOrgName(originOrgInfo.getOrgName());
        return model;
    }

    private OrgInfo findParentInfo(Node node) {
        return null == node.parent ? node.info : node.parent.info;
    }

    /**
     * 比较第一个元素和第一个元素, 找不到就向上找, 知道相等或者到达更目录也不行
     *
     * @param thisNode 第一个元素
     * @param thatNode 第二个元素
     * @return 节点
     */
    private Node getTheNodeIsUpAtThatNode(Node thisNode, Node thatNode) {
        if (thisNode.id.equals(thatNode.id)) {
            return thisNode;
        } else {
            if (null == thisNode.parent) {
                return null;
            } else {
                return getTheNodeIsUpAtThatNode(thisNode.parent, thatNode);
            }
        }
    }






    /**
     * 在id为parent的父节点下添加id为child的子节点, 为null时代表父节点是根节点
     *
     * @param child 子节点
     * @param parentId 父id
     */
    public void addChildFor(OrgInfo child, Integer parentId) {
        if (null == child) {
            throw new IllegalArgumentException("树添加子节点必须传child");
        }
        assertContainsId(child.getId());
        Node parentNode = null;
        boolean isAddRoot = false;
        if (null == parentId) {
            if (null != root) {
                throw new IllegalArgumentException("给有root的树添加子节点是不能不传parent的");
            } else {
                isAddRoot = true;
            }
        } else {
            parentNode = getAndAssertNotContainsId(parentId);
        }
        Node childNode = new Node(child.getId(), child.isOrigin(), child);
        id2NodeMap.put(child.getId(), childNode);
        if (isAddRoot) {
            root = childNode;
        } else {
            childNode.parent = parentNode;
            addChildToParent(parentNode, childNode);
        }
    }


    /**
     * 或许根节点id
     *
     * @return 根节点id
     */
    public Integer getRootId() {
        return root.id;
    }

    private OrgInfo findOriginOrgInfo(Node node){
        return findOriginNode(node).info;
    }

    /**
     * 找到指定元素的origin元素, 如果到最后都没有就返回root, 如果他自己就是origin 立马返回
     *
     * @param node 指定节点
     * @return 节点
     */
    private Node findOriginNode(Node node) {
        if (node.origin || node == root) {
            return node;
        }
        return findOriginNode(node.parent);
    }

    private void addChildToParent(Node parentNode, Node childNode) {
        List<Node> children = parentNode.children;
        if (null == children) {
            children = new ArrayList<>(8);
            parentNode.children = children;
        }
        children.add(childNode);
    }

    private void assertContainsId(Integer child) {
        if (id2NodeMap.containsKey(child)) {
            throw new IllegalArgumentException("树禁止有两个相同的节点");
        }
    }

    private Node getAndAssertNotContainsId(Integer child) {
        if (!id2NodeMap.containsKey(child)) {
            throw new IllegalArgumentException("树禁止有两个相同的节点");
        }
        return id2NodeMap.get(child);
    }

    private void compareCheck(Integer first, Integer second) {
        assertHasAnyNull(first, second);
        assertNotInThisTree(first);
        assertNotInThisTree(second);
    }

    private void assertNotInThisTree(Integer id) {
        if (!id2NodeMap.containsKey(id)) {
            throw new IllegalArgumentException("改元素没有在树上");
        }
    }

    private void assertHasAnyNull(Integer first, Integer second) {
        if (!ObjectUtils.allNotNull(first, second)) {
            throw new IllegalArgumentException("比较时不能传空");
        }
    }

}
